import asyncio
import json
import os
import sys

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from contextlib import AsyncExitStack
from openai import OpenAI
from typing import Optional
from dotenv import load_dotenv

load_dotenv()

class MCPClient:
    def __init__(self) -> None:
        """
        初始化MCP客户端
        """
        self.exit_stack = AsyncExitStack()
        self.api_key = os.getenv("API_KEY")
        self.base_url = os.getenv("BASE_URL")
        self.model = os.getenv("MODEL")
        self.sessions: dict[str, ClientSession] = {}
        self.tools_map: dict[str, str] = {}
        
        if not self.base_url:
            raise ValueError("BASE_URL environment variable is required")
        
        self.client = OpenAI(base_url=self.base_url, api_key=self.api_key)

    async def connect_to_server(self, server_id: str, script_path: str):
        """
        连接到MCP服务器
        """
        if server_id in self.sessions:
            raise ValueError(f"服务端 {server_id} 已经连接")
        
        is_python = script_path.endswith(".py")
        is_js = script_path.endswith(".js")
        if not (is_python or is_js):
            raise ValueError(f"服务脚本 {script_path} 必须以 .py 或 .js 结尾")
        
        command = "python" if is_python else "node"
        server_params = StdioServerParameters(
            command=command,
            args=[script_path],
            env=os.environ.copy(),
        )

        # 启动 MCP 服务器并建立通信
        stdio_transport = await self.exit_stack.enter_async_context(
            stdio_client(server_params)
        )
        stdio, write = stdio_transport
        session = await self.exit_stack.enter_async_context(ClientSession(stdio, write))
        await session.initialize()
        self.sessions[server_id] = {"session": session, "stdio": stdio, "write": write}
        print(f"已连接到 MCP 服务器: {server_id}")

        # 获取 MCP 服务器上的工具列表
        response = await session.list_tools()
        tools = response.tools
        for tool in tools:
            self.tools_map[tool.name] = server_id
        # print("\n已连接到服务器，支持以下工具:", [tool.name for tool in tools])

    async def list_tools(self):
        """列出所有服务端的工具"""
        if not self.sessions:
            print("没有已连接的服务端")
            return

        print("已连接的服务端工具列表:")
        for tool_name, server_id in self.tools_map.items():
            print(f"工具: {tool_name}, 来源服务端: {server_id}")

    async def process_query(self, query: str):
        """
        处理用户查询
        """
        messages = [{"role": "user", "content": query}]

        # 构建统一的工具列表
        available_tools = []
        for tool_name, server_id in self.tools_map.items():
            session = self.sessions[server_id]["session"]
            response = await session.list_tools()
            for tool in response.tools:
                if tool.name == tool_name:
                    available_tools.append({
                        "type": "function",
                        "function": {
                            "name": tool.name,
                            "description": tool.description,
                            "input_schema": tool.inputSchema
                        }
                    })

        print('整合的服务端工具列表:', available_tools)

        # 循环处理工具调用
        while True:
            # 请求 OpenAI 模型处理
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=available_tools
            )

            # 处理返回的内容
            content = response.choices[0]
            if content.finish_reason == "tool_calls":
                # 执行工具调用
                for tool_call in content.message.tool_calls:
                    tool_name = tool_call.function.name
                    tool_args = json.loads(tool_call.function.arguments)

                    # 根据工具名称找到对应的服务端
                    server_id = self.tools_map.get(tool_name)
                    if not server_id:
                        raise ValueError(f"未找到工具 {tool_name} 对应的服务端")

                    session = self.sessions[server_id]["session"]
                    result = await session.call_tool(tool_name, tool_args)
                    print(f"\n\n[Calling tool {tool_name} on server {server_id} with args {tool_args}]\n\n")

                    # 将工具调用的结果添加到 messages 中
                    messages.append({
                        "role": "tool",
                        "content": result.content[0].text,
                        "tool_call_id": tool_call.id,
                    })

            else:
                # 如果没有工具调用，返回最终的回复
                return content.message.content

    async def chat_loop(self):
        """运行交互式聊天循环"""
        print("\n🤖 MCP 客户端已启动！输入 'quit' 退出")
        while True:
            try:
                query = input("\n你: ").strip()
                if query.lower() == 'quit':
                    break
                response = await self.process_query(query) # 发送用户输入到 OpenAI
                API_response = self.client.chat.completions.create(
                    model=self.model,
                    messages=[{"role": "user", "content": response}],
                )
                print(f"\n🤖 OpenAI: {API_response.choices[0].message.content}")
            except Exception as e:
                print(f"\n⚠️ 发生错误: {str(e)}")
    
    async def cleanup(self):
        """清理资源"""
        await self.exit_stack.aclose()

async def main():
    client = MCPClient()
    try:
        await client.connect_to_server('weather', '../mcp-server/weather/weather.py')
        await client.connect_to_server('filesystem', '../mcp-server/filesystem/filesystem.py')
        # 列出 MCP 服务器上的工具
        await client.list_tools()
        await client.chat_loop()
    finally:
        await client.cleanup()

if __name__ == "__main__":
    asyncio.run(main())